package IC.LIR;

import IC.AST.ArrayLocation;
import IC.AST.Assignment;
import IC.AST.BinaryOp;
import IC.AST.Break;
import IC.AST.CallStatement;
import IC.AST.Continue;
import IC.AST.Expression;
import IC.AST.ExpressionBlock;
import IC.AST.Field;
import IC.AST.Formal;
import IC.AST.ICClass;
import IC.AST.If;
import IC.AST.Length;
import IC.AST.LibraryMethod;
import IC.AST.Literal;
import IC.AST.LocalVariable;
import IC.AST.LogicalBinaryOp;
import IC.AST.LogicalUnaryOp;
import IC.AST.MathBinaryOp;
import IC.AST.MathUnaryOp;
import IC.AST.Method;
import IC.AST.NewArray;
import IC.AST.NewClass;
import IC.AST.PrimitiveType;
import IC.AST.Program;
import IC.AST.Return;
import IC.AST.Statement;
import IC.AST.StatementsBlock;
import IC.AST.StaticCall;
import IC.AST.StaticMethod;
import IC.AST.This;
import IC.AST.UserType;
import IC.AST.VariableLocation;
import IC.AST.VirtualCall;
import IC.AST.VirtualMethod;
import IC.AST.Visitor;
import IC.AST.While;

public class LabelingVisitor implements Visitor {

	public Object visit(Program program) {
        
		int label = 0;
        
        for (ICClass currentClasses: program.getClasses()){
        	currentClasses.accept(this);
			int temp = currentClasses.getLabel();
			label = Math.max(temp, label);
        }
        
        program.setLabel(label);
        return null;
	}


	public Object visit(ICClass icClass) {
		
        int label = 0;
        
        for (Method currentMethod: icClass.getMethods()){
        	currentMethod.accept(this);
			int temp = currentMethod.getLabel();
			label = Math.max(temp, label);
        }
        icClass.setLabel(label);
        return null;
	}


	public Object visit(Field field) {
		field.setLabel(0);
		return null;
	}


	public Object visit(VirtualMethod method) {
		return calculateMethodLabel(method);
	}


	public Object visit(StaticMethod method) {
		return calculateMethodLabel(method);
	}


	public Object visit(LibraryMethod method) {
		method.setLabel(0);
		return null;
	}


	public Object visit(Formal formal) {
		formal.setLabel(0);
		return null;
	}


	public Object visit(PrimitiveType type) {
		type.setLabel(0);
		return null;
	}


	public Object visit(UserType type) {
		type.setLabel(0);
		return null;

	}


	public Object visit(Assignment assignment) {
		assignment.getAssignment().accept(this);
		assignment.getVariable().accept(this);
		
		int assignmentLabel = assignment.getAssignment().getLabel();
		int variableLabel = assignment.getVariable().getLabel();
		
		assignment.setLabel(calculateLabel(assignmentLabel, variableLabel));
		return null;
	}


	public Object visit(CallStatement callStatement) {
		callStatement.getCall().accept(this);
		callStatement.setLabel(callStatement.getCall().getLabel());
		return null;
	}


	public Object visit(Return returnStatement) {
		if (returnStatement.hasValue()){
			returnStatement.getValue().accept(this);
			returnStatement.setLabel(returnStatement.getLabel());
		}
		else{
			returnStatement.setLabel(0);
		}
		return null;
	}


	public Object visit(If ifStatement) {
		
		ifStatement.getCondition().accept(this);
		ifStatement.getOperation().accept(this);
		
		int conditionLabel = ifStatement.getCondition().getLabel();
		int operationLabel = ifStatement.getOperation().getLabel();
		
		int label = Math.max(conditionLabel, operationLabel);
		
		if (ifStatement.hasElse()){
			ifStatement.getElseOperation().accept(this);
			label = Math.max(label, ifStatement.getElseOperation().getLabel());
		}
		
		ifStatement.setLabel(label);
		return null;
	}


	public Object visit(While whileStatement) {
		whileStatement.getCondition().accept(this);
		whileStatement.getOperation().accept(this);
		
		int conditionLabel = whileStatement.getCondition().getLabel();
		int operationLabel = whileStatement.getOperation().getLabel();
		
		whileStatement.setLabel(Math.max(conditionLabel, operationLabel));
		return null;
	}


	public Object visit(Break breakStatement) {
		breakStatement.setLabel(0);
		return null;
	}


	public Object visit(Continue continueStatement) {
		continueStatement.setLabel(0);
		return null;
	}


	public Object visit(StatementsBlock statementsBlock) {
		
		int label = 0;
		
		for (Statement stmt : statementsBlock.getStatements()){
			stmt.accept(this);
			int temp = stmt.getLabel();
			label = Math.max(temp, label);
		}
                
		statementsBlock.setLabel(label);
		return null;
	}


	public Object visit(LocalVariable localVariable) {
		if (localVariable.getInitValue() != null){
			localVariable.getInitValue().accept(this);
			localVariable.setLabel(localVariable.getInitValue().getLabel());
		}
		else {
			localVariable.setLabel(0);
		}
		return null;
	}


	public Object visit(VariableLocation location) {
       
        if (location.isExternal()){
           location.getLocation().accept(this);
           location.setLabel(location.getLocation().getLabel());
        }
        else{
        	location.setLabel(0);
        }
        return null;
	}


	public Object visit(ArrayLocation location) {
		location.getArray().accept(this);
		location.getIndex().accept(this);
		
		int arrayLabel = location.getArray().getLabel();
		int indexLabel = location.getIndex().getLabel();
		location.setLabel(calculateLabel(arrayLabel, indexLabel));
		
		return null;
	}


	public Object visit(StaticCall call) {
		
		for (Expression expr : call.getArguments()){
			expr.accept(this);
		}
		
		call.setLabel(Integer.MAX_VALUE);
		return null;
	}


	public Object visit(VirtualCall call) {
		
		if (call.isExternal()){
			call.getLocation().accept(this);
		}
		
		for (Expression expr : call.getArguments()){
			expr.accept(this);
		}
		
		call.setLabel(Integer.MAX_VALUE);
		return null;
	}


	public Object visit(This thisExpression) {
		thisExpression.setLabel(0);
		return null;
	}


	public Object visit(NewClass newClass) {
		newClass.setLabel(0);
		return null;
	}


	public Object visit(NewArray newArray) {
		newArray.getSize().accept(this);
		newArray.setLabel(newArray.getLabel());
		return null;
	}


	public Object visit(Length length) {
		length.getArray().accept(this);
		length.setLabel(length.getArray().getLabel());
		return null;
	}


	public Object visit(MathBinaryOp binaryOp) {
		return calculateBinaryOpLabel(binaryOp);
	}


	public Object visit(LogicalBinaryOp binaryOp) {
		return calculateBinaryOpLabel(binaryOp);
	}


	public Object visit(MathUnaryOp unaryOp) {
		unaryOp.getOperand().accept(this);
		unaryOp.setLabel(unaryOp.getOperand().getLabel());
		return null;
	}


	public Object visit(LogicalUnaryOp unaryOp) {
		unaryOp.getOperand().accept(this);
		unaryOp.setLabel(unaryOp.getOperand().getLabel());
		return null;
	}


	public Object visit(Literal literal) {
		literal.setLabel(0);
		return null;
	}


	public Object visit(ExpressionBlock expressionBlock) {
		expressionBlock.getExpression().accept(this);
		expressionBlock.setLabel(expressionBlock.getExpression().getLabel());
		return null;

	}

	
	private Object calculateMethodLabel(Method method){
        int label = 0;
        for (Statement stmt: method.getStatements()){
        	stmt.accept(this);
			int temp = stmt.getLabel();
			label = Math.max(temp, label);
        }
        
        method.setLabel(label);
        return null;
	}

	private Object calculateBinaryOpLabel(BinaryOp binaryOp) {

		binaryOp.getFirstOperand().accept(this);
		binaryOp.getSecondOperand().accept(this);

		int leftLabel = binaryOp.getLabel();
		int rightLabel = binaryOp.getLabel();
		
		binaryOp.setLabel(calculateLabel(leftLabel, rightLabel));

		return null;
	}
	
	private int calculateLabel(int firstLabel, int secondLabel){
		if (firstLabel == secondLabel){
			if (firstLabel == Integer.MAX_VALUE){
				return firstLabel;
			}
			return firstLabel + 1;
		}
		else{
			return Math.max(firstLabel, secondLabel);
		}	
	}

}